<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * Reads file to process its lines.
 *
 * @category   spica
 * @package    core
 * @subpackage utils
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      October 30, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: FileReader.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaFileReader
{
    /**
     * Full qualified file path.
     *
     * @var string
     */
    protected $_filePath;

    /**
     * File handle.
     */
    protected $_fp;

    /**
     * Is file pointer closed?
     *
     * @var bool
     */
    protected $_isClosed = false;

    /**
     * The current file line.
     *
     * @var int
     */
    protected $_currentLine;

    /**
     * Constructs an object of <code>SpicaFileReader</code>.
     *
     * @param string $filePath
     * @param string $mode
     */
    public function __construct($filePath, $mode = "r")
    {
        $this->_filePath = $filePath;
        $this->_fp = fopen($filePath, $mode);
    }

    /**
     * Moves file pointer to a given line in a file.
     *
     * @throws Exception
     * @param  int $line Zero-based line number
     * @return int|false The file position on success or false on error
     */
    public function moveToLine($line)
    {
        if (empty($this->_fp))
        {
            throw new Exception('Unable to get a file pointer.');
        }

        if ($line < 0)
        {
            throw new Exception(sprintf('Argument $line (%s) is not valid.', $line));
        }

        if (-1 === $this->moveToHead())
        {
            return false;
        }

        if (0 === $line)
        {
            $this->_currentLine = 0;
            return 0;
        }

        $lc = 0; // Line counter

        while (false !== ($char = fgetc($this->_fp)))
        {
            // PHP_EOL: CRLF (i.e. \r\n) or LF (\n)
            if ($char === "\n")
            {
                $lc++;
            }

            if ($line === $lc)
            {
                $this->_currentLine = $line;
                return ftell($this->_fp);
            }
        }

        return false;
    }

    /**
     * Gets the string of the current line.
     *
     * @return string
     */
    public function readCurrentLine()
    {
        return fgets($this->_fp);
    }

    /**
     * Reads lines from the line numbered $from to $to
     *
     * @throws Exception
     * @param  int $from Zero-based starting line
     * @param  int $to End line
     * @return string on success or false on error
     */
    public function readBetween($from, $to)
    {
        if ($from > $to)
        {
            throw new Exception(sprintf('Argument $from (%s) must be smaller than or equal to argument $to (%s)', $from, $to));
        }

        if (empty($this->_fp))
        {
            throw new Exception('Unable to get a file pointer.');
        }

        if (false === $this->moveToLine($from))
        {
            return false;
        }

        $nl    = $to - $from; // Number of line
        $lines = array();

        while (false === feof($this->_fp))
        {
            if ($nl < 0)
            {
                break;
            }

            $line = fgets($this->_fp);

            if (false === $line)
            {
                break;
            }

            $lines[] = trim($line, "\n\r");
            $nl--;
        }

        $this->_currentLine = $from + $nl;
        return implode("\n", $lines);
    }

    /**
     * Reads the last line in a file.
     *
     * FIXME How to calculate the total line
     * @return A string on success or false on error
     */
    public function readLastLine()
    {
        if (-1 === $this->moveToHead())
        {
            return false;
        }

        $pos = -1;
        $t   = ' ';

        while ($t != "\n")
        {
            fseek($this->_fp, $pos, SEEK_END);

            if (ftell($this->_fp) == 0)
            {
                break;
            }

            $t = fgetc($this->_fp);
            $pos--;
        }

        return fgets($this->_fp);
    }

    /**
     * Reads the first line in a file.
     *
     * @return A string on success or false on error
     */
    public function readFirstLine()
    {
        if (-1 === $this->moveToHead())
        {
            return false;
        }

        $this->_currentLine = 0;
        return fgets($this->_fp);
    }

    /**
     * Moves the file pointer to the beginning of the file.
     *
     * @return int returns 0 on success; otherwise, returns -1.
     */
    public function moveToHead()
    {
        return fseek($this->_fp, 0, SEEK_SET);
    }

    /**
     * Moves the file pointer to the end of the file.
     *
     * @return int returns 0 on success; otherwise, returns -1.
     */
    public function moveToEnd()
    {
        return fseek($this->_fp, -1, SEEK_END);
    }

    /**
     * Gets the current file pointer position.
     *
     * @return int
     */
    public function getCurrentPosition()
    {
        return ftell($this->_fp);
    }

    /**
     * Gets file size.
     *
     * @return int the total bytes
     */
    public function getFileSize()
    {
        return filesize($this->_filePath);
    }

    /**
     * Get current file line number. For getting the current line content @see #readCurrentLine()
     *
     * @return An int if the current line is detectable or null if it is undefined
     */
    public function getCurrentLine()
    {
        return $this->_currentLine;
    }

    /**
     * Closes file handle.
     */
    public function close()
    {
        fclose($this->_fp);
        $this->_fp = null;
        $this->_isClosed = true;
    }

    /**
     * Destroys the instance.
     */
    public function __destruct()
    {
        if (false === $this->_isClosed)
        {
            $this->close();
        }
    }
}

?>